#include <stdio.h>
#include "Raptor.h"

#if 1
#define msg(a) printf a
#else
#define msg(a)
#endif



enum KeyCode {VK_LEFT, VK_RIGHT, VK_UP, VK_DOWN, VK_SPACE, VK_ENTER, VK_ESC};

enum Color{
	clBlack = 0,
			clBlue,
			clGreen,
			clCyan,
			clRed,
			clMagenta,
			clYellow,
			clWhite
};

enum Sound{
	sRaptor,
	sHelicopter,
	sMig,
	sMissile,
	sExplosion,
	sAid,
	sGameOver,
	sRaptorHit
};

enum ObjectType{
	otRaptor = 0,
			otHelicopter,
			otMig,
			otMissile,
			otExplosion,
			otBullet,
			otAid
};

enum Direction{
	dirUp,
	dirDown,
	dirHanging,
	dirLeft,
	dirRight,
	dirNone // for bullets
};

enum ShootPosition{
	spLeft,
	spRight,
	spCenter
};



typedef struct {
	int msStamp; // millisecond
	int tsCount; // thirty second count
	int interval; // ms for next event

	int randomInterval; // 1 if random
	int randomMin; // min interval
	int randomMax; // max interval
} timeStamp;

typedef struct {
	int objId;	// Object id (indicates what address object
	// is saved to)
	int x;	// sprites x position (left to right)
	int y; // sprites y position (top to bottom)

	enum Color color; // sprites colour
	enum ObjectType type; // type of sprite
	int enabled; 	// if object is enabled.
	int destroyed; // =1 when object is destroyed
	// To disable graphics, x and y must be
	// outside of screen area.
	// Therefore this value must be disabled by setObjEnabled,
	// so that x and y is set outside of screen area when
	// disabled.

	int isEnemy;	// enemy=1 for enemy objects (for collision detection)

	// indicates that object has been changed,
	// so that we don't save unchanged data.
	int positionHasChanged;
	int idHasChanged;

	int collisionInspect; // only check for collision if value > 0
	int collisionImpact; // damage made to colliding object
	int maxhealth; // used for raptor
	int health; // health of object. When health is 0 or less, object is destroyed
	int pointsForDestroy; // points earned for destroying object

	int moveLength; // how many pixels object is moved
	timeStamp moveTime; // timestamp for speed
	timeStamp shotTime; // timestamp for shot speed
	int shotEnabled; // if object has shots enabled

	enum Direction direction; // direction of flight, only straight lines implemented
	enum ShootPosition shootPosition; // only Raptor uses this at the moment

} baseObj;


void getObjAddressOffset(enum ObjectType type, int* start, int* end);
void getTypeImgSize(enum ObjectType type, int* width, int* height);

// create objects
void createObjects();
baseObj* createRaptor();
void createHelicopters(int type);
baseObj* createHelicopter(int x, int y, int type, enum Direction direction);
void createMigs(int type);
baseObj* createMig(int x, int y, int type);
baseObj* createBullet(int x, int y);
baseObj* createAid(int x, int y);
baseObj* createExplosion(int xCenter, int yCenter, int isHit);
baseObj* createBaseObj(int id, int x,int y, enum Color color, enum ObjectType type, int isEnemy);

// base object functions
void setObjX(int value, baseObj* obj);
void setObjY(int value, baseObj* obj);
void setObjEnabled(int value, baseObj* obj);
int getObjVGAID(baseObj* obj);
void saveBaseObj(baseObj* obj);
int getNewObjectId(enum ObjectType type);
void clearEnabledObjects();

// object control functions
void raptorShoot(baseObj* obj);
void moveRaptor();
void moveObjects();
void moveObject(baseObj* obj);
void shootObject(baseObj* obj); // controls objects shoot function


// collision detection functions
void collisionDetection();
void collisionCheck(int obj1Start, int obj1End, int obj2Start, int obj2End);
int collision(baseObj* a, baseObj* b);
int getCollisionRadius(baseObj* obj);
void damageControl(baseObj* obj, int doBlowUp);
void blowUp(baseObj* obj, int destroy);

// general game functions
void setGameOver();
void setScore(int Value);
void addToScore(int Value);
void displayScore(int val);
void playSound(enum Sound sound);
void displayHealth();
void updateDifficulty();
void initialize();

// general functions
int min(int v1, int v2);
int max(int v1, int v2);
int random(int min, int max);
void updateTimeStamp(timeStamp* time, int interval, int updateInterval);
int wait(timeStamp* stamp);
int keyPressed(enum KeyCode key);




// global variables
unsigned int* IO = (unsigned int*)IO_BASE;
int score = 0;
int gameOver = 0;
int difficultyLevel;
timeStamp difficultyTime;
timeStamp collisionTestTime;
timeStamp objCreateTime;

// arrays storing baseobjects & relevant info
int enabledObjects[OBJECT_COUNT];
baseObj baseObjects[OBJECT_COUNT];


/*
 * getObjAddressOffset()
 * returns start and end offsets (index in main object array) for objecttype
 */
void getObjAddressOffset(enum ObjectType type, int* start, int* end){
	switch(type){
	case(otRaptor):
		*start=RAPTOR_ADDR;
		*end=RAPTOR_ADDR;
		break;
	case(otHelicopter):
		*start=HELICOPTER_ADDR_START;
		*end=HELICOPTER_ADDR_END;
		break;
	case(otMig):
		*start=MIG_ADDR_START;
		*end=MIG_ADDR_END;
		break;
	case(otMissile):
		*start=MISSILE_ADDR_START;
		*end=MISSILE_ADDR_END;
		break;
	case(otExplosion):
		*start=EXPLOSION_ADDR_START;
		*end=EXPLOSION_ADDR_END;
		break;
	case(otBullet):
		*start=BULLET_ADDR_START;
		*end=BULLET_ADDR_END;
	break;
	case(otAid):
		*start=AID_ADDR_START;
	*end=AID_ADDR_END;
	break;
	default:;
	}
}

/*
 * getTypeImgSize()
 * returns imge width and height for object type
 */
void getTypeImgSize(enum ObjectType type, int* width, int* height){
	switch(type){
	case(otRaptor):
		*width=RAPTOR_IMG_WIDTH;
		*height=RAPTOR_IMG_HEIGHT;
		break;
	case(otHelicopter):
		*width=HELICOPTER_IMG_WIDTH;
		*height=HELICOPTER_IMG_HEIGHT;
		break;
	case(otMig):
		*width=MIG_IMG_WIDTH;
		*height=MIG_IMG_HEIGHT;
		break;
	case(otMissile):
		*width=MISSILE_IMG_WIDTH;
		*height=MISSILE_IMG_HEIGHT;
		break;
	case(otExplosion):
		*width=EXPLOSION_IMG_WIDTH;
		*height=EXPLOSION_IMG_HEIGHT;
		break;
	case(otBullet):
		*width=BULLET_IMG_WIDTH;
		*height=BULLET_IMG_HEIGHT;
		break;
	case(otAid):
		*width=AID_IMG_WIDTH;
		*height=AID_IMG_HEIGHT;
		break;
	default:;
	}
}

/*
 * createObjects()
 * creates random objects (enemies and first aid)
 */
void createObjects(){
	int objType;
	int w, h;
	int screenLeft = SCREEN_LEFT;
	int screenRight = SCREEN_RIGHT;

	if (wait(&objCreateTime)){
		objType = random(0,min((difficultyLevel+1)*4,20));

		switch(objType){
			case 0:
				getTypeImgSize(otAid, &w, &h);
				createAid(random(screenLeft,screenRight-w),0);
				break;
			case 1:
			case 2:
			case 3:
			case 4:
				createMigs(0); // slower mig - shoots
				break;
			case 5:
			case 6:
			case 7:
			case 8:
				createHelicopters(0); // helicopter that stops to shoot
				break;
			case 9:
			case 10:
			case 11:
			case 12:
				createMigs(1); // fast mig, doesn't shoot
				break;
			case 13:
			case 14:
			case 15:
			case 16:
				createHelicopters(1); // flies in a curved path
				break;
			case 17:
			case 18:
			case 19:
			case 20:
				createHelicopters(2); // seeks towards raptor
				break;
			default: ;
		}
	}
}

/*
 * createRaptor()
 * creates a raptor, returns pointer to object
 */
baseObj* createRaptor(){
	baseObj* o = createBaseObj(RAPTOR_ADDR,350,480,clWhite,otRaptor,0);

	o->maxhealth = 8;
	o->health = o->maxhealth; // max for raptor
	o->collisionImpact = 1000; // everything gets destroyed when colliding with raptor
	o->moveTime.interval = 25;
	o->shotTime.interval = 150;
	o->shootPosition = spRight;

	playSound(sRaptor);
	return o;
}

/*
 * createHelicopters()
 * Creates formations of helicopters
 */
void createHelicopters(int type){
	//todo: helicopter types
	// three types of helicopters:
	// 0. flies in a straight line, stops a while (can be horizontal or vertical)
	// 1. flies in a curve (enters vertical, leaves horizontal)
	// 2. seeks towards raptor

	// formations:
	// 0. vert line
	// 1. hor line

	// todo: formation types
	// formation type:
	// 0. normal
	// 1. split (two lines mirrored horizontaly by screen center)

	int i, x, y;
	int noOfHelicopters;
	int formation, /*formationType,*/ posOffset;
	int dirVal;
	enum Direction direction;
	int screenLeft, screenRight, screenTop, screenBottom;
	int imgWidth, imgHeight, imgOffset;

	imgOffset = 20;
	screenLeft = SCREEN_LEFT;
	screenRight = SCREEN_RIGHT;
	screenTop = SCREEN_TOP;
	screenBottom = SCREEN_BOTTOM;
	getTypeImgSize(otHelicopter, &imgWidth, &imgHeight);

	noOfHelicopters = random(1,min(difficultyLevel*2,8));

	if(noOfHelicopters>1){
		formation = random(0,1);
		//formationType = random(0,1);
		posOffset = random(-3,3);
	} else{
		formation = 0;
		//formationType = random(0,1);
		posOffset = 0;
	}

	// direction:
	// 1 entering from left (direction: right),
	// 2 entering from right (direction: left),
	// 3 & 4 entering from top (direction: down)
	dirVal = random(1,4);
	switch(dirVal){
		case(1): x = 0-((noOfHelicopters-1)*((imgWidth*formation)+((1-formation)*max(posOffset,0)*imgOffset)));
				 y = random(screenTop, screenBottom/2);
				 direction = dirRight;
				 break;
		case(2): x = (screenRight)-((1-formation)*min(posOffset,0)*imgOffset*(noOfHelicopters-1));
				 y = random(screenTop, screenBottom/2);
				 direction = dirLeft;
				 break;
		case(3):
		case(4): x = random(screenLeft, screenRight-imgWidth);
				 y = 0-((noOfHelicopters-1)*((max(posOffset,0)*imgOffset*formation)+(imgHeight*(1-formation))));
				 direction = dirDown;
				 break;
		default:;
	}

	y = 0;
	x = random(screenLeft, screenRight);
	for(i=0; i<noOfHelicopters; i++){
		createHelicopter(x, y, type, direction);

		// coordinates for next helicopter

		// todo: mirror if formation type = 1!
		if(formation){
			// horizontal line
			x = x+imgWidth;
			y = y+(posOffset*imgOffset);
		} else{
			// vertical line
			x = x+(posOffset*imgOffset);
			y = y+imgHeight;
		}
	}

	playSound(sHelicopter);
}

/*
 * createHelicopter()
 * Creates a helicopter, returns a pointer to obj
 */
baseObj* createHelicopter(int x, int y, int type, enum Direction direction){
	int id;
	baseObj* o = 0;
	id = getNewObjectId(otHelicopter);
	if (id>=0){
		o = createBaseObj(id,x,y,clGreen,otHelicopter,1);
		o->health = 3;
		o->collisionImpact = 1;
		o->direction = direction;
		o->moveLength = 3;
		o->moveTime.interval = 20;
		o->pointsForDestroy = 5;
		o->shotEnabled = 1;
		// less time between shots as difficulty level increases
		o->shotTime.interval = 2000-max(1500,(difficultyLevel*100));
	}
	return o;
}

/*
 * createMigs()
 * creates formations of MIGs.
 */
void createMigs(int type){
	// two types of migs:
	// 0. flies slow(er) and shoots
	// 1. fast, doesn't shoot

	// formations:
	// 0. horizontal (can rise or fall)
	// 1. v formation (can also be completely horizontal)

	int i, x, y;
	int noOfMigs;
	int formation, yOffset;
	int screenLeft, screenRight, imgWidth, imgHeight;

	screenLeft = SCREEN_LEFT;
	screenRight = SCREEN_RIGHT;
	getTypeImgSize(otMig, &imgWidth, &imgHeight);

	// no of migs increase as difficulty level increases
	noOfMigs = random(1,min(difficultyLevel*2,8));

	if(noOfMigs>1){
		formation = random(0,1);
		yOffset = random(-3,3);
	} else{
		formation = 0;
		yOffset = 0;
	}

	y = (max(yOffset,0)*(noOfMigs-1)*(-20));
	x = random(screenLeft, screenRight);
	for(i=0; i<noOfMigs; i++){
		// create mig
		createMig(x, y, type);

		// coordinates for next mig
		x = x+imgWidth;
		if(x>(screenRight-imgWidth)){
			x = screenLeft;
		}

		if((formation==1) && (i>(noOfMigs/2))){
			// v formation
			y = y-(yOffset*20);
		} else{
			// straight formation
			y = y+(yOffset*20);
		}
	}

	playSound(sMig);
}

/*
 * createMig
 * Creates a MIG
 */
baseObj* createMig(int x, int y, int type){
	int id;
	baseObj* o = 0;
	id = getNewObjectId(otMig);
	if (id>=0){
		if(type==0){
			// slower mig
			o = createBaseObj(id,x,y,clBlue,otMig,1);
			o->moveTime.interval = 30;
			o->moveLength = 6;
			o->shotEnabled = 1;
			o->pointsForDestroy = 2;
			o->shotTime.interval = 1000;
		} else {
			// faster mig (gets faster as difficulty increases)
			o = createBaseObj(id,x,y,clMagenta,otMig,1);
			o->moveTime.interval = 20-min(difficultyLevel,10);
			o->pointsForDestroy = 4;
			o->shotEnabled = 0;
		}
		o->health = 1;
		o->collisionImpact = 1;
		o->direction = dirDown;
	}
	return o;
}

/*
 * createBullet()
 * creates a bullet
 */
baseObj* createBullet(int x, int y){
	int id;
	baseObj* o = 0;
	id = getNewObjectId(otBullet);
	if (id>=0){
		o = createBaseObj(id,x,y,clYellow,otBullet,1);
		o->health = 1;
		o->collisionImpact = 1;
		o->direction = dirDown;
		o->moveTime.interval = 40;
	}
	return o;
}

/*
 * createAid()
 * creates a first aid obj
 */
baseObj* createAid(int x, int y){
	int id;
	baseObj* o = 0;
	id = getNewObjectId(otAid);
	if (id>=0){
		o = createBaseObj(id,x,y,clMagenta,otAid,1);
		o->health = 1;
		o->collisionImpact = -2;
		o->pointsForDestroy = 3;
		o->direction = dirDown;
		o->moveTime.interval = 70;
	}
	return o;
}

/*
 * createExplosion()
 * creates an explosion, returns a pointer to explosion obj
 */
baseObj* createExplosion(int xCenter, int yCenter, int isHit){
	int id, x, y;
	baseObj* o = 0;
	id = getNewObjectId(otExplosion);
	if (id>0){
		x = xCenter-(EXPLOSION_IMG_WIDTH/2);
		y = yCenter-(EXPLOSION_IMG_HEIGHT/2);

		o = createBaseObj(id,x,y,clRed,otExplosion,0);
		o->collisionInspect = 0;
		o->destroyed = 1;
		o->direction = dirHanging;
		updateTimeStamp(&(o->moveTime), 750, 1);
	}

	if(isHit){
		playSound(sRaptorHit);
	} else{
		playSound(sExplosion); // play sound even if no explosion is displayed
	}
	return o;
}

/*
 * wait()
 * returns 1 if time+interval has elapsed.
 * We only check for intervals up to 30 seconds (at the moment)
 */
int wait(timeStamp* stamp){
	timeStamp time;
	int retVal = 0;
	int max_ms = 30000;
	int nextTime;
	int hasOccurred = 0;

	time.randomInterval = 0;
	updateTimeStamp(&time,-1,1);

	if((stamp->tsCount)==time.tsCount){
		if(stamp->msStamp+stamp->interval<time.msStamp){
			hasOccurred = 1;
		}
	} else if ((time.tsCount-stamp->tsCount)==1){
		if(stamp->interval-(max_ms-stamp->msStamp)<time.msStamp){
			hasOccurred = 1;
		}
	} else if (time.tsCount-stamp->tsCount>1){
		hasOccurred = 1;
	}

	if(hasOccurred){
		stamp->msStamp = time.msStamp;
		stamp->tsCount = time.tsCount;
		retVal = 1;
	}
	return retVal;
}

/*
 * raptorShoot()
 * controls raptors shooting
 */
void raptorShoot(baseObj* obj){
	// i=-1 if no missile can be assigned
	int i = getNewObjectId(otMissile);
	int xOffset;
	baseObj* missile;

	// wait for shot interval
	if((i>-1) && wait(&(obj->shotTime))){
		if (obj->shootPosition==spRight){
			// raptors missile launches from right
			xOffset = 36;
			// next missile should be from left
			obj->shootPosition=spLeft;
		} else{
			// raptors missile launches from left
			xOffset = 2;
			// next missile should be from right
			obj->shootPosition=spRight;
		}
		// create missile and set values
		missile = createBaseObj(i,obj->x+xOffset,obj->y+40,clRed,otMissile,0);
		missile->health = 1;
		missile->collisionImpact = 2;
		missile->direction = dirUp;
		missile->moveTime.interval = 18;

		playSound(sMissile);
	}
}

/*
 * createBaseObj()
 * creates a base object (struct) and sets baseObj in array.
 * returns pointer to object.
 */
baseObj* createBaseObj(int id, int x,int y, enum Color color, enum ObjectType type, int isEnemy){
	baseObj b;

	b.objId = id;
	b.x = x;
	b.y = y;
	b.color = color;
	b.type = type;

	b.isEnemy = isEnemy;
	b.enabled = 1;
	b.destroyed = 0;
	b.pointsForDestroy = 0;

	b.idHasChanged = 1;
	b.positionHasChanged = 1;
	saveBaseObj(&b);

	b.moveLength = 10;
	b.shotTime.randomInterval = 0;
	b.moveTime.randomInterval = 0;
	b.shotEnabled = 0;
	updateTimeStamp(&(b.shotTime),-1,0);
	updateTimeStamp(&(b.moveTime),-1,0);

	b.collisionInspect = 1;
	b.collisionImpact = 0;

	// update arrays
	enabledObjects[b.objId]=1;
	baseObjects[b.objId]=b;

	return getBaseObj(b.objId);
}

/*
 * setObjX()
 * sets x value of obj
 * Somewhat unnecessary, but oh well :)
 */
void setObjX(int value, baseObj* obj){
	if (value!=obj->x){
		obj->x = value;
		obj->positionHasChanged = 1;
	}
}

/*
 * setObjY()
 * sets y value of obj
 */
void setObjY(int value, baseObj* obj){
	if (value!=obj->y){
		obj->y = value;
		obj->positionHasChanged = 1;
	}
}

/*
 * setObjEnabled()
 * For enabling/disabling baseObj
 */
void setObjEnabled(int value, baseObj* obj){
	obj->enabled = value;
	// if object is disabled, move outside of
	// screen area, so that it is not painted.
	if (!value){
		setObjX(0,obj);
		setObjY(0,obj);
		saveBaseObj(obj);
		enabledObjects[obj->objId] = 0;
	}
}

/*
 * saveBaseObj()
 * Saves values of obj that are related to VGA.
 */
void saveBaseObj(baseObj* obj){
	if (obj->idHasChanged){
		IO[VGA_ID+obj->objId]=getObjVGAID(obj);
	}
	if (obj->positionHasChanged){
		IO[VGA_X+obj->objId]=obj->x;
		IO[VGA_Y+obj->objId]=obj->y;
	}
	obj->positionHasChanged = 0;
	obj->idHasChanged = 0;
}


/*
 * getObjVGAID()
 * gets ID for VGA controller.
 * OLD: Return value is (hex) 0x00CT,
 * 		where C is colour and T is Type.
 * Return value is now only object's colour.
 */
int getObjVGAID(baseObj* obj){
	int val = obj->color;//*16+obj->type;
	return val;
}

/*
 * moveRaptor()
 * Controls raptor, both movement and shooting
 */
void moveRaptor(){
	baseObj* obj = getBaseObj(RAPTOR_ADDR);
	int x = obj->x;
	int y = obj->y;
	int borderVal = 0;

	// wait for raptor's movetime
	if(wait(&obj->moveTime)){
		// check if arrow keys are pressed and
		// move raptor

		if (keyPressed(VK_UP)){
			y = y-10;
			if(y<SCREEN_TOP){
				y = SCREEN_TOP;
			}
		}
		if (keyPressed(VK_DOWN)){
			y = y+10;
			borderVal = SCREEN_BOTTOM-RAPTOR_IMG_HEIGHT;
			if (y>borderVal){
				y = borderVal;
			}
		}
		if (keyPressed(VK_RIGHT)){
			x = x+10;
			borderVal = SCREEN_RIGHT-RAPTOR_IMG_WIDTH;
			if(x>borderVal){
				x = borderVal;
			}
		}
		if (keyPressed(VK_LEFT)){
			x = x-10;
			if(x<SCREEN_LEFT){
				x = SCREEN_LEFT;
			}
		}
		// if space is pressed, shoot.
		// should actually be outside of if statement,
		// but since it works pretty well here, we
		// just keep it here for now.
		if (keyPressed(VK_SPACE)){
			raptorShoot(obj);
		}

		// enter is sometimes used for test
		if (keyPressed(VK_ENTER)){
			//createHelicopter(x,10);
		}

		setObjX(x,obj);
		setObjY(y,obj);
		saveBaseObj(obj);
	}
}

/*
 * moveObjects()
 * Controls all objects except raptor.
 * Controls movement and shooting.
 */
void moveObjects(){
	int i;

	// don't move raptor (RAPTOR_ADDR)
	for (i=OBJECT_COUNT-1; i>-1; i--){
		if (i!=RAPTOR_ADDR){
			if(enabledObjects[i]){
				// control objects movement
				moveObject(getBaseObj(i));
				// control objects shooting
				shootObject(getBaseObj(i));
			}
		}
	}
}

/*
 * moveObject()
 * moves object, also disables objects that have been
 * set to be destroyed
 */
void moveObject(baseObj* obj){
	int y = obj->y;
	int x = obj->x;

	int imgWidth, imgHeight;
	int outOfBoundsUp, outOfBoundsDown;
	int outOfBoundsLeft, outOfBoundsRight;
	int outOfBounds;

	getTypeImgSize(obj->type, &imgWidth, &imgHeight);

	outOfBoundsUp = SCREEN_TOP-imgHeight;
	outOfBoundsDown = SCREEN_BOTTOM;
	outOfBoundsLeft = SCREEN_LEFT-imgWidth;
	outOfBoundsRight = SCREEN_RIGHT;

	// wait for objects move time
	if (wait(&obj->moveTime)){
		// disable object if object is destroyed
		if (obj->destroyed){
			setObjEnabled(0,obj);
		} else{
			switch(obj->direction){
				case dirHanging: break;
				case dirUp: y = y-obj->moveLength; break;
				case dirDown: y = y+obj->moveLength; break;
				case dirLeft: x = x-obj->moveLength; break;
				case dirRight: x = x+obj->moveLength; break;
				default: y = y+obj->moveLength;
			}
/*
			if (obj->type==otHelicopter){
				switch(obj->direction){
				case dirHanging: break;
				case dirDown: y = y+obj->moveLength; break;
				case dirLeft: x = x-obj->moveLength; break;
				case dirRight: x = x+obj->moveLength; break;
				default: y = y+3;
				}
			} else{
				if (obj->direction==dirUp){
					y = y-obj->moveLength;
				} else if (obj->direction==dirDown){
					y = y+obj->moveLength;
				}
			}
*/
			setObjX(x,obj);
			setObjY(y,obj);
			saveBaseObj(obj);


			// check if object has left screen borders after move
			switch(obj->direction){
			case(dirUp): outOfBounds = (y<outOfBoundsUp); break;
			case(dirDown): outOfBounds = (y>outOfBoundsDown); break;
			case(dirLeft): outOfBounds = (x<outOfBoundsLeft); break;
			case(dirRight): outOfBounds = (x>outOfBoundsRight); break;
			default: outOfBounds = (y<outOfBoundsUp || y>outOfBoundsDown ||
					y<outOfBoundsLeft || y>outOfBoundsRight);
			}

			// disable objects that are out of bounds
			if (outOfBounds)
			{
				setObjEnabled(0,obj);
			}
		}
	}
}

/*
 * shootObject()
 * Controls objects shooting
 */
void shootObject(baseObj* obj){
	int y = obj->y;
	int x = obj->x;

	int imgWidth, imgHeight;
	int outOfBoundsUp;
	int outOfBoundsLeft, outOfBoundsRight;

	// shots must be enabled
	// wait for timer
	if (obj->shotEnabled && wait(&obj->shotTime)){
		outOfBoundsUp = SCREEN_TOP-imgHeight;
		outOfBoundsLeft = SCREEN_LEFT-imgWidth;
		outOfBoundsRight = SCREEN_RIGHT;

		// we let objects shoot even though they are
		// above screen borders.
		if(/*(y<outOfBoundsUp) && */(x>outOfBoundsLeft) && (x<outOfBoundsRight)){
			getTypeImgSize(obj->type, &imgWidth, &imgHeight);
			createBullet(x+imgWidth/2, y+imgHeight/2);
		}
	}
}

/*
 * keyPressed()
 * returns 1 if key is down
 */
int keyPressed(enum KeyCode key){
	int keyboard = IO[IO_PS2KBD_D];

	int val;
	switch(key){
	case VK_UP: val = 0x0001u; break;
	case VK_RIGHT: val = 0x0002u; break;
	case VK_LEFT: val = 0x0004u; break;
	case VK_DOWN: val = 0x0008u; break;
	case VK_SPACE: val = 0x0010u; break;
	case VK_ENTER: val = 0x0020u; break;
	case VK_ESC: val = 0x0040u; break;
	default: val = 0;
	}

	return keyboard&val;
}

/*
 * disables all objects and moves them out of screen area
 */
void clearEnabledObjects(){
	int i=0;
	for(i=0; i<OBJECT_COUNT; i++){
		enabledObjects[i]=0;

		IO[VGA_ID+i] = 0;
		IO[VGA_X+i] = 0;
		IO[VGA_Y+1] = 0;
	}
}

// gets largest available id for object
// if no objects are available, then function returns -1
int getNewObjectId(enum ObjectType type){
	int i;
	int retVal = -1;
	int start, end;
	getObjAddressOffset(type,&start,&end);
	for(i=start;i>=end;i--){
		if (!enabledObjects[i]){
			retVal = i;
			break;
		}
	}
	return retVal;
}

/*
 * collisionDetection()
 * Main collision detection function.
 * Checks for collisions.
 */
void collisionDetection(){
	if(wait(&collisionTestTime)){
		//updateTimeStamp(&collisionTestTime,-1);
		collisionCheck(RAPTOR_ADDR, RAPTOR_ADDR, HELICOPTER_ADDR_START, MIG_ADDR_END);
		collisionCheck(RAPTOR_ADDR, RAPTOR_ADDR, AID_ADDR_START, AID_ADDR_END);
		collisionCheck(RAPTOR_ADDR, RAPTOR_ADDR, BULLET_ADDR_START, BULLET_ADDR_END);
		collisionCheck(HELICOPTER_ADDR_START, MIG_ADDR_END, MISSILE_ADDR_START, MISSILE_ADDR_END);
	}
}

/*
 * collisionCheck()
 * Check collision between all objects in object array that
 * specified by their indexes.
 * collisionCheck(1,2,6,8) will check collisions between:
 * obj[1] and obj[6]
 * obj[1] and obj[7]
 * obj[1] and obj[8]
 * obj[2] and obj[6]
 * obj[2] and obj[7]
 * obj[2] and obj[8]
 *
 * indexes are only used to speed up the check.
 */
void collisionCheck(int obj1Start, int obj1End, int obj2Start, int obj2End){
	int i,j;
	baseObj* obj1;
	baseObj* obj2;

	for(i=obj1Start;i>=obj1End;i--){
		obj1 = getBaseObj(i);
		// only check if object is enabled and collisionInspect is enabled
		if (enabledObjects[i] && obj1->collisionInspect){
			for(j=obj2Start;j>=obj2End;j--){
				obj2 = getBaseObj(j);
				// same for object 2
				if (enabledObjects[j] && obj2->collisionInspect){
					// objects must be on opposite sides!
					if (obj2->isEnemy!=obj1->isEnemy){
						if (collision(obj1,obj2)){
							// set objects health, mustn't exceed objects maxhealth
							obj1->health=min(obj1->maxhealth,(obj1->health-obj2->collisionImpact));
							obj2->health=min(obj2->maxhealth,(obj2->health-obj1->collisionImpact));
							// check if objects are to be destroyed
							damageControl(obj1, obj2->type!=otAid);
							damageControl(obj2, obj1->type!=otAid);
							// play sound if colliding with first aid
							if ((obj1->type==otAid)|| (obj2->type==otAid)){
								playSound(sAid);
							}
						}

					}
				}
			}
		}
	}
}

/*
 * collision()
 * Checks if two objects are colliding.
 * Returns 1 if objects are colliding.
 */
int collision(baseObj* a, baseObj* b){
	int x1,x2,y1,y2,r;
	int colRa, colRb, colR;
	int squareDistX, squareDistY;
	int img1Height, img2Height;
	int img1Width, img2Width;
	int maxHeight, maxWidth;

	getTypeImgSize(a->type, &img1Width, &img1Height);
	getTypeImgSize(b->type, &img2Width, &img2Height);

	// find largest image width and height
	// (for checking if borders of images collide)
	maxWidth = max(img1Width, img2Width);
	maxHeight = max(img1Height, img2Height);

	// only check if both objects are enabled
	if(a->enabled && b->enabled){
		squareDistX = (a->x-b->x);
		if (squareDistX<0){
			squareDistX = squareDistX*(-1);
		}
		squareDistY = (a->y-b->y);
		if (squareDistY<0){
			squareDistY = squareDistY*(-1);
		}
		// if x distance is less than the largest image width and
		// y distance is less than the largest image height, then
		// borders collide.
		// Now check collision radius.
		if((squareDistX<maxWidth) && (squareDistY<maxHeight)){
			// calculate coordinates for centrum of the "circels" that define the objects
			x1 = a->x + (img1Width/2);
			y1 = a->y + (img1Height/2);
			x2 = b->x + (img2Width/2);
			y2 = b->y + (img2Height/2);

			// calculate distance between objects using Pythagoras.
			// (don't take square root)
			r = ((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
			// collision radius is radius of a + radius of b
			colRa = getCollisionRadius(a);
			colRb = getCollisionRadius(b);
			colR = (colRa+colRb);
			// since we didn't use square root, we need to
			// calculate collision radius in the power of two.
			if(r < (colR*colR)){
				return 1; // they are colliding
			}
		}
	}
	return 0; // they are NOT colliding
}

/*
 * min()
 * returns min of the two arguments
 */
int min(int v1, int v2){
	int val;
	if((unsigned int)v1< (unsigned int)v2){
		val = v1;
	} else{
		val = v2;
	}
	return val;
}

/*
 * max()
 * returns max of the two arguments
 */
int max(int v1, int v2){
	int val;
	if(v1>v2){
		val = v1;
	} else{
		val = v2;
	}
	return val;
}

/*
 * random()
 * Returns a random value between min and max
 * (both values included)
 */
int random(int min, int max){
	int val;
	int interval=(max+1)-min;
	// time is based on ms timer at the moment
	// should be cpu clk.
	// todo: change to cpu clk!
	int time = IO[IO_MSTIMER_D];

	// don't want to divide with 0 or
	// find random value if min==max
	if(interval!=0 && max!=min){
		val = (time%interval)+min;
	} else{
		val = min;
	}

	return val;

}

/*
 * updateTimeStamp()
 * Get actual time stamps and set interval value
 */
void updateTimeStamp(timeStamp* time, int interval, int updateInterval){
	time->msStamp = IO[IO_MSTIMER_D];
	time->tsCount = IO[IO_TSTIMER_D];

	if(updateInterval){
		if(time->randomInterval){
			time->interval = random(time->randomMin, time->randomMax);
		} else if(interval>=0){
			time->interval = interval;
		}
	}
}

/*
 * getCollisionRadius()
 * returns radius for object type
 */
int getCollisionRadius(baseObj* obj){
	int val;

	switch(obj->type){
	case otRaptor: val = 22; break;
	case otHelicopter: val = 23; break;
	case otMig: val = 23; break;
	case otMissile: val = 3; break;
	case otBullet: val = 2; break;
	case otAid: val = 16; break;
	default: val = 0; // no collision
	}

	return val;
}

/*
 * damageControl()
 * Checks objects health to see if it should be destroyed.
 * if not doBlowUp, then we ignore function. This is just
 * so that we don't show an explosion when raptor hits
 * a first aid. (dirty ad-hoc workaround!!)
 */
void damageControl(baseObj* obj, int doBlowUp){
	// destroy obj if health is less than or equal to 0
	if (doBlowUp){
		if (obj->health<=0){
			// points if enemy is destroyed (even if by colliding into it)
			if (obj->isEnemy){
				addToScore(obj->pointsForDestroy);
			}

			switch(obj->type){
			case otHelicopter:
			case otMig: blowUp(obj,1); break;
			case otRaptor: blowUp(obj, 1); setGameOver(); break;
			default: setObjEnabled(0,obj);
			}
		} else{
			if(obj->type==otRaptor){
				blowUp(obj,0); // don't destroy
			}
		}
	}
}

/*
 * setGameOver()
 * Call function when game is over.
 */
void setGameOver(){
	gameOver=1;
	playSound(sGameOver);
	//printf("\n Points: %d \n", score);
}

/*
 * blowUp()
 * Disables object and shows an explosion on top of object.
 * If destroy=0 then we only show an explosion, this is
 * used for when raptor is hit.
 */
void blowUp(baseObj* obj, int destroy){
	int x,y,width,height;
	int yOffset;

	if (destroy){
		// instead of destroying a object
		// we let it hang for 10 mseconds before destroying.
		// moveObjects() takes care of destroying.

		// indicate that obj is destroyed
		obj->destroyed = 1;
		// don't inspect collision for object anymore
		obj->collisionInspect = 0;
		// let obj hang still until destroyed
		obj->direction = dirHanging;
		// next move, well destroy, in 10 ms
		updateTimeStamp((&obj->moveTime),10,1);
	}

	// show explosion
	getTypeImgSize(obj->type, &width, &height);
	x = obj->x + (width/2);
	if (obj->type==otRaptor){
		yOffset = 0;
	} else{
		yOffset = 15; // below center
	}
	y = (obj->y + (height/2))+yOffset; // just below center for raptor
	createExplosion(x,y, !destroy);
}

/*
 * setScore()
 * Sets score to value.
 */
void setScore(int Value){
	score = Value;
	displayScore(score);
}

/*
 * addToScore()
 * Adds value to score
 */
void addToScore(int Value){
	score = score+Value;
	displayScore(score);
}

/*
 * displayScore()
 * Displays score on DIO4's 7 segment
 */
void displayScore(int val){
	IO[IO_SSEG_D] = val;
}

/*
 * playSound()
 * Sends information to com port.
 */
void playSound(enum Sound sound){
	// we keep it simple and send one integer pr sound.
	switch(sound){
		case(sRaptor): printf("1"); break;
		case(sHelicopter): printf("2"); break;
		case(sMig): printf("3"); break;
		case(sMissile): printf("4"); break;
		case(sExplosion): printf("5"); break;
		case(sAid): printf("6"); break;
		case(sGameOver): printf("7"); break;
		case(sRaptorHit): printf("8"); break;
		default:;
	}
}
/*
 * displayHealth()
 * Shows raptors health on DIO4's leds.
 * There are eight leds, so one led pr health point.
 */
void displayHealth(){
	int h=0;
	baseObj* o;

	// get raptors health
	if (enabledObjects[RAPTOR_ADDR]){
		o = getBaseObj(RAPTOR_ADDR);
		h = o->health;
	}

	// write health to leds
	switch(h){
	case(1): IO[IO_LED_D] = 0x0001u; break;
	case(2): IO[IO_LED_D] = 0x0003u; break;
	case(3): IO[IO_LED_D] = 0x0007u; break;
	case(4): IO[IO_LED_D] = 0x000Fu; break;
	case(5): IO[IO_LED_D] = 0x001Fu; break;
	case(6): IO[IO_LED_D] = 0x003Fu; break;
	case(7): IO[IO_LED_D] = 0x007Fu; break;
	case(8): IO[IO_LED_D] = 0x00FFu; break;
	default: IO[IO_LED_D] = 0;
	}
}

/*
 * updateDifficulty()
 * difficulty level is updated every 20 (or so) seconds.
 * Update interval is defined in Raptor.h (DIFFICULTY_LEVEL_INTERVAL).
 */
void updateDifficulty(){
	if(wait(&difficultyTime)){
		difficultyLevel = difficultyLevel+1;
		//updateTimeStamp(&difficultyTime,-1);
	}
}

/*
 * main()
 * Main game function. Runs in an infinite loop.
 */
int main(){
	int startGame;
	// infinite game loop
	while (1){
		startGame = 1;

		// main game loop.
		// restarts when escape is pressed.
		while(!keyPressed(VK_ESC)){
			// initialize before starting a (new) game
			if(startGame){
				startGame=0;
				initialize();
			}

			// if game over, we don't do anything,
			// just wait for escape to be pressed
			if(!gameOver){
				// control raptor
				moveRaptor();
				// create random objects (enemies+first aid)
				createObjects();
				// control (move & shoot) all objects
				moveObjects();
				// collision detect
				collisionDetection();
				// we keep checking raptors health
				// and writing to the leds.
				// This could instead be done in
				// collisionDetection().
				displayHealth();
				// update difficulty every 20 (or so) seconds
				updateDifficulty();
			}
		}
	}

	return 1;
}



/*
 * initialize()
 * resets and initializes variables.
 */
void initialize(){
	int interval;
	int diffInterval;

	interval = COLLISIONTEST_INTERVAL;
	diffInterval = DIFFICULTY_LEVEL_INTERVAL

	// make sure all objects are disabled
	clearEnabledObjects();

	gameOver = 0;
	updateTimeStamp(&collisionTestTime, interval, 1);

	difficultyLevel = 0;
	difficultyTime.randomInterval = 0;
	updateTimeStamp(&difficultyTime, diffInterval, 1);

	objCreateTime.randomInterval = 1;
	objCreateTime.randomMin = OBJECTCREATE_INTERVAL_MIN;
	objCreateTime.randomMax = OBJECTCREATE_INTERVAL_MAX;
	updateTimeStamp(&objCreateTime,-1, 1);

	setScore(0);
	// now create a raptor
	createRaptor();
}
